﻿using System;
using System.Diagnostics.Contracts;
using System.IO;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal struct DXFN
    {
        private bool alchNinch;
        private bool alcvNinch;
        private bool cIndentNinch;
        private DXFALC dxfalc;
        private DXFBdr dxfbdr;
        private DXFFntD dxffntd;
        private DXFNum dxfnum;
        private DXFPat dxfpat;
        private DXFProt dxfprot;
        private bool fIfmtUser;
        private bool fIfntNinch;
        private bool fMergeCellNinch;
        private bool fNewBorder;
        private bool fShrinkNinch;
        private bool fZeroInited;
        private bool flsNinch;
        private bool glBottomNinch;
        private bool glDiagDownNinch;
        private bool glDiagUpNinch;
        private bool glLeftNinch;
        private bool glRightNinch;
        private bool glTopNinch;
        private bool hiddenNinch;
        private bool iReadingOrderNinch;
        private bool ibitAtrAlc;
        private bool ibitAtrBdr;
        private bool ibitAtrFnt;
        private bool ibitAtrNum;
        private bool ibitAtrPat;
        private bool ibitAtrProt;
        private bool icvBNinch;
        private bool icvFNinch;
        private bool ifmtNinch;
        private bool kintoNinch;
        private bool lockedNinch;
        private bool trotNinch;
        private bool _unused1;
        private bool _unused2;
        private bool wrapNinch;

        private byte Rh
        {
            get
            {
                byte result = 0;
                if (fZeroInited) result |= 0x80;
                result |= reserved22;
                return result;
            }
        }

        private byte Er
        {
            get
            {
                byte result = 0;
                if (fIfmtUser) result |= 0x01;
                if (_unused2) result |= 0x02;
                //                if (unused2 = (er & 0x01) != 0;
                if (fNewBorder) result |= 0x04;
                result = (byte) (result | (reserved21 << 3));
                return result;
            }
        }

        private byte Wd
        {
            get
            {
                byte result = 0;
                if (w2) result |= 0x0001;
                if (ibitAtrNum) result |= 0x0002;
                if (ibitAtrFnt) result |= 0x0004;
                if (ibitAtrAlc) result |= 0x0008;
                if (ibitAtrBdr) result |= 0x00010;
                if (ibitAtrPat) result |= 0x00020;
                if (ibitAtrProt) result |= 0x00040;
                if (iReadingOrderNinch) result |= 0x00080;
                return result;
            }
        }

        public byte Qw
        {
            get
            {
                byte result = 0;
                if (flsNinch) result |= 0x0001;
                if (icvFNinch) result |= 0x02;
                if (icvBNinch) result |= 0x04;
                if (ifmtNinch) result |= 0x08;
                if (fIfntNinch) result |= 0x10;
                if (_unused1)
                    result |= 0x20;
                //unused1 = (qw & 0x20) != 0;
                if (reserved11) result |= 0x40; /// 3
                if (reserved12) result |= 0x80; /// 3
                return result;
            }
        }

        public byte Ip
        {
            get
            {
                byte result = 0;
                if (lockedNinch) result |= 0x01;
                if (hiddenNinch) result |= 0x02;
                if (glLeftNinch) result |= 0x04;
                if (glRightNinch) result |= 0x08;
                if (glTopNinch) result |= 0x010;
                if (glBottomNinch) result |= 0x20;
                if (glDiagDownNinch) result |= 0x40;
                if (glDiagUpNinch) result |= 0x80;
                return result;
            }
        }

        public byte Ah
        {
            get
            {
                byte result = 0;
                if (alchNinch) result |= 0x1;
                if (alcvNinch) result |= 0x2;
                if (wrapNinch) result |= 0x4;
                if (trotNinch) result |= 0x8;
                if (kintoNinch) result |= 0x10;
                if (cIndentNinch) result |= 0x20;
                if (fShrinkNinch) result |= 0x40;
                if (fMergeCellNinch) result |= 0x80;
                return result;
            }
        }


#if DEBUG
        private bool inTest;
#endif
        private bool reserved11;
        private bool reserved12;
        private byte reserved21;
        private byte reserved22;
        private bool w2;

        public void Read(BinaryReader reader)
        {
#if DEBUG
            ReadWriteChecker.CheckStruct((ref DXFN t, BinaryReader r) => t.Read(r),
                                         (ref DXFN t, BinaryWriter w) => t.Write(w), reader, ref this);
            long startPosition = reader.BaseStream.Position;
#endif

            byte ah = reader.ReadByte();
            byte ip = reader.ReadByte();
            byte qw = reader.ReadByte();
            byte wd = reader.ReadByte();
            byte er = reader.ReadByte();
            byte rh = reader.ReadByte();
            alchNinch = (ah & 0x1) != 0;
            alcvNinch = (ah & 0x2) != 0;
            wrapNinch = (ah & 0x4) != 0;
            trotNinch = (ah & 0x8) != 0;
            kintoNinch = (ah & 0x10) != 0;
            cIndentNinch = (ah & 0x20) != 0;
            fShrinkNinch = (ah & 0x40) != 0;
            fMergeCellNinch = (ah & 0x80) != 0;
            Contract.Assert((ah) == Ah);

            lockedNinch = (ip & 0x01) != 0;
            hiddenNinch = (ip & 0x02) != 0;
            glLeftNinch = (ip & 0x04) != 0;
            glRightNinch = (ip & 0x08) != 0;
            glTopNinch = (ip & 0x010) != 0;
            glBottomNinch = (ip & 0x20) != 0;
            glDiagDownNinch = (ip & 0x40) != 0;
            glDiagUpNinch = (ip & 0x80) != 0;
            Contract.Assert(ip == Ip);

            flsNinch = (qw & 0x0001) != 0;
            icvFNinch = (qw & 0x02) != 0;
            icvBNinch = (qw & 0x04) != 0;
            ifmtNinch = (qw & 0x08) != 0;
            fIfntNinch = (qw & 0x10) != 0;
            _unused1 = (qw & 0x20) != 0;
            reserved11 = (qw & 0x040) != 0; /// 3
            reserved12 = (qw & 0x080) != 0; /// 3
            Contract.Assert(Qw == qw);
            /// 
            w2 = (wd & 0x0001) != 0;
            ibitAtrNum = (wd & 0x0002) != 0;
            ibitAtrFnt = (wd & 0x0004) != 0;
            ibitAtrAlc = (wd & 0x0008) != 0;
            ibitAtrBdr = (wd & 0x00010) != 0;
            ibitAtrPat = (wd & 0x00020) != 0;
            ibitAtrProt = (wd & 0x00040) != 0;
            iReadingOrderNinch = (wd & 0x00080) != 0;
            Contract.Assert(wd == Wd);
            fIfmtUser = (er & 0x01) != 0;
            _unused2 = (er & 0x02) != 0;
            fNewBorder = (er & 0x04) != 0;
            reserved21 = (byte) (er >> 3);
            fZeroInited = (rh & 0x80) != 0;
            reserved22 = (byte) (rh & 0x7F);
            Contract.Assert(ah == Ah);
            Contract.Assert(er == Er);
            Contract.Assert(ip == Ip);
            //\\  Contract.Assert(qw == Qw);
            Contract.Assert(rh == Rh);
            Contract.Assert(wd == Wd);
            Contract.Assert(rh == Rh);

            if (ibitAtrNum)
            {
                dxfnum = DXFNum.Create(fIfmtUser);
                dxfnum.Read(reader);
            }
            if (ibitAtrFnt)
            {
                dxffntd = new DXFFntD();
                dxffntd.Read(reader);
            }
            if (ibitAtrAlc)
            {
                dxfalc = new DXFALC();
                dxfalc.Read(reader);
            }
            if (ibitAtrBdr)
            {
                dxfbdr = new DXFBdr();
                dxfbdr.Read(reader);
            }
            if (ibitAtrPat)
            {
                dxfpat = new DXFPat();
                dxfpat.Read(reader);
            }
            if (ibitAtrProt)
            {
                dxfprot = new DXFProt();
                dxfprot.Read(reader);
            }
#if DEBUG
            if (inTest)
                return;
            inTest = true;
            long endPosition = reader.BaseStream.Position;
            var ms = new MemoryStream(100000000);
            var writer = new BinaryWriter(ms);
            Write(writer);
            Contract.Assert(writer.BaseStream.Position == (endPosition - startPosition));
            var readBytes = new byte[writer.BaseStream.Position];
            var writeBytes = new byte[writer.BaseStream.Position];
            ms.Position = 0;
            ms.Read(writeBytes, writeBytes.Length, 0);
            reader.BaseStream.Position = startPosition;
            reader.BaseStream.Read(readBytes, readBytes.Length, 0);
            for (int i = 0; i < readBytes.Length; i++)
                Contract.Assert(readBytes[i] == writeBytes[i]);
            reader.BaseStream.Position = endPosition;
            inTest = false;
#endif
        }

        public void SetFromDifferencialStyleRecord(DifferentialStyleRecord record, BiffWriterWorkbookContext context)
        {
            Contract.Ensures(_unused1);
            Contract.Ensures(_unused2);
            _unused1 = true;
            icvFNinch = true;
            alcvNinch = true;
            alchNinch = true;
            alcvNinch = true;
            wrapNinch = true;
            trotNinch = true;
            kintoNinch = true;
            cIndentNinch = true;
            fShrinkNinch = true;
            fMergeCellNinch = true;
            lockedNinch = true;
            hiddenNinch = true;
            glLeftNinch = true;
            glRightNinch = true;
            glTopNinch = true;
            glBottomNinch = true;
            glDiagDownNinch = true;
            glDiagUpNinch = true;
            flsNinch = true;
            icvFNinch = true;
            ifmtNinch = true;
            fIfntNinch = true;
            fZeroInited = true;
            iReadingOrderNinch = true;
            _unused1 = true;
            _unused2 = true;
            if (record.Background != null)
            {
                icvBNinch = false;
                ibitAtrPat = false;
                icvFNinch = false;
                var patternFill = (PatternFill) record.Background; //\\ type?
                dxfpat.icvBackground = context.GetColor(patternFill.BackgroundColor, IcvXF.DefaultBackground);
                dxfpat.icvForeground = context.GetColor(patternFill.ForegroundColor, IcvXF.DefaultForeground);
                dxfpat.fls = XFRecord.ConvertToBiff(patternFill.Type);
            }
        }

        public DifferentialStyleRecord ToDifferencialStyleRecord(BiffReaderWorkbookContext context,
                                                                 IPropertyProvider cfExRecord)
        {
            var result = new DifferentialStyleRecord();
            if (!icvBNinch || !icvFNinch)
            {
                SpreadsheetColor backgroundColor = null;
                if (!icvBNinch)
                    backgroundColor = context.GetColor(dxfpat.icvBackground, IcvXF.DefaultBackground);
                SpreadsheetColor foregroundColor = null;
                if (!icvFNinch)
                    foregroundColor = context.GetColor(dxfpat.icvForeground, IcvXF.DefaultForeground);
                PatternType type = PatternType.Solid;
                if (!flsNinch)
                {
                    type = XFRecord.ConvertFromBiff(dxfpat.fls);
                }
                result.Background = new PatternFill(foregroundColor, backgroundColor, type);
            }
            if (!alcvNinch)
                result.EnsureAlignment().VerticalAlignment = (VerticalCellAlignment) this.dxfalc.Alcv;
            if (!alchNinch)
                if (this.dxfalc.Alch != 0xFF)
                    result.EnsureAlignment().HorizontalAlignment = (HorizontalCellAlignment) this.dxfalc.Alch;
            if (!wrapNinch)
                result.EnsureAlignment().WrapText = this.dxfalc.WrapText;
            if (!trotNinch)
                result.EnsureAlignment().TextRotation = dxfalc.TextRotation;
            if (!kintoNinch)
                result.EnsureAlignment().LastLineJustified = dxfalc.JustLast;
            if (!cIndentNinch)
                result.EnsureAlignment().Indentation = dxfalc.Indentation;
            if (!fShrinkNinch)
                result.EnsureAlignment().ShrinkToFit = dxfalc.ShrinkToFit;
            if (!fMergeCellNinch)
                result.EnsureAlignment().MergeCell = dxfalc.MergeCell;
            if (!lockedNinch)
                result.EnsureProtection().Locked = dxfprot.Locked;
            if (!hiddenNinch)
                result.EnsureProtection().Hidden = dxfprot.Hidden;
            if (!ifmtNinch)
                result.NumberFormat = dxfnum.GetNumberFormat();
            if (!fIfntNinch)
                throw new NotImplementedException();
            Border leftBorder = null,
                   rightBorder = null,
                   topBorder = null,
                   bottomBorder = null,
                   diagonalDownBorder = null,
                   diagonalUpBorder = null;

            if (!glLeftNinch)
                leftBorder = CreateBorder(dxfbdr.DgLeft, dxfbdr.IcvLeft, ExtPropType.LeftBorderColor, context,
                                          cfExRecord);
            if (!glRightNinch)
                rightBorder = CreateBorder(dxfbdr.DgRight, dxfbdr.IcvRight, ExtPropType.RightBorderColor, context,
                                           cfExRecord);
            if (!glTopNinch)
                topBorder = CreateBorder(dxfbdr.DgTop, dxfbdr.IcvTop, ExtPropType.TopBorderColor, context, cfExRecord);
            if (!glBottomNinch)
                bottomBorder = CreateBorder(dxfbdr.DgBottom, dxfbdr.IcvBottom, ExtPropType.BottomBorderColor, context,
                                            cfExRecord);
            if (!glDiagDownNinch && dxfbdr.BitDiagDown)
                diagonalDownBorder = CreateBorder(dxfbdr.DgDiag, dxfbdr.IcvDiag, ExtPropType.DiagonalBorderColor,
                                                  context, cfExRecord);
            if (!glDiagUpNinch && dxfbdr.BitDiagUp)
                diagonalUpBorder = CreateBorder(dxfbdr.DgDiag, dxfbdr.IcvDiag, ExtPropType.DiagonalBorderColor, context,
                                                cfExRecord);
            if (
                Enumerable.Any(
                    new object[]
                        {leftBorder, rightBorder, topBorder, bottomBorder, diagonalDownBorder, diagonalUpBorder},
                    o => o != null))
            {
                result.Borders = new Borders
                                     {
                                         Left = leftBorder,
                                         Right = rightBorder,
                                         Top = topBorder,
                                         Bottom = bottomBorder,
                                         DiagonalDown = diagonalDownBorder,
                                         DiagonalUp = diagonalUpBorder
                                     };
            }
            if (cfExRecord != null)
            {
                var extendedGradient = cfExRecord.GetPropertyValue<XFExtGradient>(ExtPropType.GradientFill);
                if (extendedGradient != null)
                    result.Background = extendedGradient.ToFill();
                else
                {
                    var extendedBackgroundColor = cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.BackgroundColor);
                    if (extendedBackgroundColor != null)
                    {
                        result.Background = new PatternFill(extendedBackgroundColor.ToColor());
                    }
                }
                FullColorExt extendedCellColor = null;
                extendedCellColor = cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.CellTextColor);
                if (extendedCellColor != null)
                    result.EnsureFont().Foreground = extendedCellColor.ToColor();

                var extendedIndent = cfExRecord.GetPropertyValue<IntExt>(ExtPropType.Indentation);
                if (extendedIndent != null)
                {
                    Contract.Assert(false, "indent");
                }
                var extendedFontScheme = cfExRecord.GetPropertyValue<IntExt>(ExtPropType.FontScheme);
                if (extendedFontScheme != null)
                {
                    Contract.Assert(false, "Font scheme");
                }
                if (cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.LeftBorderColor) != null ||
                    cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.TopBorderColor) != null ||
                    cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.RightBorderColor) != null ||
                    cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.BottomBorderColor) != null ||
                    cfExRecord.GetPropertyValue<FullColorExt>(ExtPropType.DiagonalBorderColor) != null)
                {
                    Contract.Assert(false, "border");
                }
            }
            return result;
        }

        private Border CreateBorder(BorderStyle style, IcvXF color, ExtPropType extendedProperty,
                                    BiffReaderWorkbookContext context, IPropertyProvider cfExRecord)
        {
            SpreadsheetColor spreadsheetColor;
            var extendedColor = cfExRecord.GetPropertyValue<FullColorExt>(extendedProperty);
            if (extendedColor != null)
            {
                spreadsheetColor = extendedColor.ToColor();
            }
            else
            {
                spreadsheetColor = style != BorderStyle.None
                                       ? context.GetColor(color)
                                       : context.GetColor(color, IcvXF.NoBorder);
            }
            if (style == BorderStyle.None && spreadsheetColor == null)
                return null;
            return new Border(style, spreadsheetColor);
        }


        public void Write(BinaryWriter writer)
        {
            Contract.Assert(!ibitAtrFnt || (dxffntd.FontName != null));
            writer.Write(Ah);
            writer.Write(Ip);
            writer.Write(Qw);
            writer.Write(Wd);
            writer.Write(Er);
            writer.Write(Rh);

            /// 

            if (ibitAtrNum)
            {
                dxfnum.Write(writer);
            }
            if (ibitAtrFnt)
            {
                dxffntd.Write(writer);
            }
            if (ibitAtrAlc)
            {
                dxfalc.Write(writer);
            }
            if (ibitAtrBdr)
            {
                dxfbdr.Write(writer);
            }
            if (ibitAtrPat)
            {
                dxfpat.Write(writer);
            }
            if (ibitAtrProt)
            {
                dxfprot.Write(writer);
            }
        }
    }
}